import logging
import os

from django.core.files.base import ContentFile
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled

from eventyay.base.i18n import language
from eventyay.base.models import (
    CachedCombinedTicket,
    CachedTicket,
    Event,
    InvoiceAddress,
    Order,
    OrderPosition,
)
from eventyay.base.services.tasks import EventTask, ProfiledTask
from eventyay.base.settings import PERSON_NAME_SCHEMES
from eventyay.base.signals import allow_ticket_download, register_ticket_outputs
from eventyay.celery_app import app
from eventyay.helpers.database import rolledback_transaction

logger = logging.getLogger(__name__)


def generate_orderposition(order_position: int, provider: str):
    order_position = OrderPosition.objects.select_related('order', 'order__event').get(id=order_position)

    with language(order_position.order.locale, order_position.order.event.settings.region):
        responses = register_ticket_outputs.send(order_position.order.event)
        for receiver, response in responses:
            prov = response(order_position.order.event)
            if prov.identifier == provider:
                filename, ttype, data = prov.generate(order_position)
                path, ext = os.path.splitext(filename)
                for ct in CachedTicket.objects.filter(order_position=order_position, provider=provider):
                    ct.delete()
                ct = CachedTicket.objects.create(
                    order_position=order_position,
                    provider=provider,
                    extension=ext,
                    type=ttype,
                    file=None,
                )
                ct.file.save(filename, ContentFile(data))
                return ct.pk


def generate_order(order: int, provider: str):
    order = Order.objects.select_related('event').get(id=order)

    with language(order.locale, order.event.settings.region):
        responses = register_ticket_outputs.send(order.event)
        for receiver, response in responses:
            prov = response(order.event)
            if prov.identifier == provider:
                filename, ttype, data = prov.generate_order(order)
                if ttype == 'text/uri-list':
                    continue

                path, ext = os.path.splitext(filename)
                for ct in CachedCombinedTicket.objects.filter(order=order, provider=provider):
                    ct.delete()
                ct = CachedCombinedTicket.objects.create(
                    order=order, provider=provider, extension=ext, type=ttype, file=None
                )
                ct.file.save(filename, ContentFile(data))
                return ct.pk


@app.task(base=ProfiledTask)
def generate(model: str, pk: int, provider: str):
    with scopes_disabled():
        if model == 'order':
            return generate_order(pk, provider)
        elif model == 'orderposition':
            return generate_orderposition(pk, provider)


class DummyRollbackException(Exception):  # NOQA: N818
    pass


def preview(event: int, provider: str):
    event = Event.objects.get(id=event)

    with (
        rolledback_transaction(),
        language(event.settings.locale, event.settings.region),
    ):
        product = event.products.create(
            name=_('Sample product'),
            default_price=42.23,
            description=_('Sample product description'),
        )
        product2 = event.products.create(name=_('Sample workshop'), default_price=23.40)

        from eventyay.base.models import Order

        order = event.orders.create(
            status=Order.STATUS_PENDING,
            datetime=now(),
            email='sample@eventyay.com',
            locale=event.settings.locale,
            expires=now(),
            code='PREVIEW1234',
            total=119,
        )

        scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
        sample = {k: str(v) for k, v in scheme['sample'].items()}
        p = order.positions.create(product=product, attendee_name_parts=sample, price=product.default_price)
        s = event.subevents.first()
        order.positions.create(
            product=product2,
            attendee_name_parts=sample,
            price=product.default_price,
            addon_to=p,
            subevent=s,
        )
        order.positions.create(
            product=product2,
            attendee_name_parts=sample,
            price=product.default_price,
            addon_to=p,
            subevent=s,
        )

        InvoiceAddress.objects.create(order=order, name_parts=sample, company=_('Sample company'))

        responses = register_ticket_outputs.send(event)
        for receiver, response in responses:
            prov = response(event)
            if prov.identifier == provider:
                return prov.generate(p)


def get_tickets_for_order(order, base_position=None):
    can_download = all([r for rr, r in allow_ticket_download.send(order.event, order=order)])
    if not can_download:
        return []
    if not order.ticket_download_available:
        return []

    providers = [response(order.event) for receiver, response in register_ticket_outputs.send(order.event)]

    tickets = []

    positions = list(order.positions_with_tickets)
    if base_position:
        # Only the given position and its children
        positions = [p for p in positions if p.pk == base_position.pk or p.addon_to_id == base_position.pk]

    for p in providers:
        if not p.is_enabled:
            continue

        if p.multi_download_enabled and not base_position:
            try:
                if len(positions) == 0:
                    continue
                ct = CachedCombinedTicket.objects.filter(order=order, provider=p.identifier, file__isnull=False).last()
                if not ct or not ct.file:
                    retval = generate_order(order.pk, p.identifier)
                    if not retval:
                        continue
                    ct = CachedCombinedTicket.objects.get(pk=retval)
                tickets.append(
                    (
                        '{}-{}-{}{}'.format(
                            order.event.slug.upper(),
                            order.code,
                            ct.provider,
                            ct.extension,
                        ),
                        ct,
                    )
                )
            except:
                logger.exception('Failed to generate ticket.')
        else:
            for pos in positions:
                try:
                    ct = CachedTicket.objects.filter(
                        order_position=pos, provider=p.identifier, file__isnull=False
                    ).last()
                    if not ct or not ct.file:
                        retval = generate_orderposition(pos.pk, p.identifier)
                        if not retval:
                            continue
                        ct = CachedTicket.objects.get(pk=retval)

                    if ct.type == 'text/uri-list':
                        continue

                    if pos.subevent:
                        # Subevent date in filename improves accessibility e.g. for screen reader users
                        fname = '{}-{}-{}-{}-{}{}'.format(
                            order.event.slug.upper(),
                            order.code,
                            pos.positionid,
                            pos.subevent.date_from.strftime('%Y_%m_%d'),
                            ct.provider,
                            ct.extension,
                        )
                    else:
                        fname = '{}-{}-{}-{}{}'.format(
                            order.event.slug.upper(),
                            order.code,
                            pos.positionid,
                            ct.provider,
                            ct.extension,
                        )
                    tickets.append((fname, ct))
                except:
                    logger.exception('Failed to generate ticket.')

    return tickets


@app.task(base=EventTask, acks_late=True)
def invalidate_cache(event: Event, product: int = None, provider: str = None, order: int = None, **kwargs):
    qs = CachedTicket.objects.filter(order_position__order__event=event)
    qsc = CachedCombinedTicket.objects.filter(order__event=event)

    if product:
        qs = qs.filter(order_position__product_id=product)

    if provider:
        qs = qs.filter(provider=provider)
        qsc = qsc.filter(provider=provider)

    if order:
        qs = qs.filter(order_position__order_id=order)
        qsc = qsc.filter(order_id=order)

    for ct in qs:
        ct.delete()
    for ct in qsc:
        ct.delete()
